/***********************************************************************
    filename:   hgeRenderer.cpp
    created:    Mon Feb 9 2009
    author:     Paul D Turner
*************************************************************************/

#include "hgeRenderer.h"
#include "hgeTexture.h"
#include "hgeGeometryBuffer.h"
#include "hgeRenderTarget.h"
#include "hgeRenderingSurface.h"
#include "hgeExceptions.h"
#include "hgeViewportTarget.h"
#include "hgeTextureTarget.h"
#include "hgeGuiSystem.h"
#include <windef.h>
#include <algorithm>

//----------------------------------------------------------------------------//
String Renderer::d_rendererID("Renderer - Official Direct3D 9 based 2nd generation renderer module.");

//----------------------------------------------------------------------------//
static const D3DMATRIX s_identityMatrix =
{
    1.0, 0.0, 0.0, 0.0,
    0.0, 1.0, 0.0, 0.0,
    0.0, 0.0, 1.0, 0.0,
    0.0, 0.0, 0.0, 1.0
};

//----------------------------------------------------------------------------//
Renderer& Renderer::create(LPDIRECT3DDEVICE9 device)
{
    return *new Renderer(device);
}

//----------------------------------------------------------------------------//
void Renderer::destroy(Renderer& renderer)
{
    delete &renderer;
}

//----------------------------------------------------------------------------//
RenderingRoot& Renderer::getDefaultRenderingRoot()
{
    return *d_defaultRoot;
}

//----------------------------------------------------------------------------//
GeometryBuffer& Renderer::createGeometryBuffer()
{
    GeometryBuffer* b = new GeometryBuffer(d_device);
    d_geometryBuffers.push_back(b);
    return *b;
}

//----------------------------------------------------------------------------//
void Renderer::destroyGeometryBuffer(const GeometryBuffer& buffer)
{
    GeometryBufferList::iterator i = std::find(d_geometryBuffers.begin(),
                                     d_geometryBuffers.end(),
                                     &buffer);

    if (d_geometryBuffers.end() != i)
    {
        d_geometryBuffers.erase(i);
        delete &buffer;
    }
}

//----------------------------------------------------------------------------//
void Renderer::destroyAllGeometryBuffers()
{
    while (!d_geometryBuffers.empty())
        destroyGeometryBuffer(**d_geometryBuffers.begin());
}

//----------------------------------------------------------------------------//
TextureTarget* Renderer::createTextureTarget()
{
    TextureTarget* t = new TextureTarget(*this);
    d_textureTargets.push_back(t);
    return t;
}

//----------------------------------------------------------------------------//
void Renderer::destroyTextureTarget(TextureTarget* target)
{
    TextureTargetList::iterator i = std::find(d_textureTargets.begin(),
                                    d_textureTargets.end(),
                                    target);

    if (d_textureTargets.end() != i)
    {
        d_textureTargets.erase(i);
        delete target;
    }
}

//----------------------------------------------------------------------------//
void Renderer::destroyAllTextureTargets()
{
    while (!d_textureTargets.empty())
        destroyTextureTarget(*d_textureTargets.begin());
}

//----------------------------------------------------------------------------//
Texture& Renderer::createTexture()
{
    Texture* tex = new Texture(*this);
    d_textures.push_back(tex);
    return *tex;
}

//----------------------------------------------------------------------------//
Texture& Renderer::createTexture(const String& filename)
{
    Texture* tex = new Texture(*this, filename);
    d_textures.push_back(tex);
    return *tex;
}

//----------------------------------------------------------------------------//
Texture& Renderer::createTexture(const Size& size)
{
    Texture* tex = new Texture(*this, size);
    d_textures.push_back(tex);
    return *tex;
}

//----------------------------------------------------------------------------//
void Renderer::destroyTexture(Texture& texture)
{
    TextureList::iterator i = std::find(d_textures.begin(),
                                        d_textures.end(),
                                        &texture);

    if (d_textures.end() != i)
    {
        d_textures.erase(i);
        delete &(texture);
    }
}

//----------------------------------------------------------------------------//
void Renderer::destroyAllTextures()
{
    while (!d_textures.empty())
        destroyTexture(**d_textures.begin());
}

//----------------------------------------------------------------------------//
void Renderer::beginRendering()
{
    d_device->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1);

    // no shaders initially
    d_device->SetVertexShader(0);
    d_device->SetPixelShader(0);

    // set device states
    d_device->SetRenderState(D3DRS_LIGHTING, FALSE);
    d_device->SetRenderState(D3DRS_FOGENABLE, FALSE);
    d_device->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);
    d_device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
    d_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
    d_device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
    d_device->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
    d_device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);

    // setup texture addressing settings
    d_device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
    d_device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);

    // setup colour calculations
    d_device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
    d_device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
    d_device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);

    // setup alpha calculations
    d_device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
    d_device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
    d_device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);

    // setup filtering
    d_device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
    d_device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);

    // disable texture stages we do not need.
    d_device->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);

    // setup scene alpha blending
    d_device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
    d_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
    d_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

    // set view matrix back to identity.
    d_device->SetTransform(D3DTS_VIEW, &s_identityMatrix);
}

//----------------------------------------------------------------------------//
void Renderer::endRendering()
{
}

//----------------------------------------------------------------------------//
void Renderer::setDisplaySize(const Size& sz)
{
    if (sz.d_x != d_displaySize.d_x && sz.d_y != d_displaySize.d_y)
    {
        d_displaySize = sz;

        // FIXME: This is probably not the right thing to do in all cases.
        hgeRect area = d_defaultTarget->getArea();
        //area.setSize(sz);
        d_defaultTarget->setArea(area);
    }
}

//----------------------------------------------------------------------------//
const Size& Renderer::getDisplaySize() const
{
    return d_displaySize;
}

//----------------------------------------------------------------------------//
const Vector2& Renderer::getDisplayDPI() const
{
    return d_displayDPI;
}

//----------------------------------------------------------------------------//
UINT Renderer::getMaxTextureSize() const
{
    return d_maxTextureSize;
}

//----------------------------------------------------------------------------//
const String& Renderer::getIdentifierString() const
{
    return d_rendererID;
}

//----------------------------------------------------------------------------//
Renderer::Renderer(LPDIRECT3DDEVICE9 device) :
    d_device(device),
    d_displaySize(getViewportSize()),
    d_displayDPI(96, 96),
    d_defaultRoot(0),
    d_defaultTarget(0)
{
    D3DCAPS9 caps;
    device->GetDeviceCaps(&caps);

    if (!caps.RasterCaps && D3DPRASTERCAPS_SCISSORTEST)
        throw RendererException("Renderer: Hardware does not support D3DPRASTERCAPS_SCISSORTEST.  Unable to proceed.");

    d_maxTextureSize = min(caps.MaxTextureHeight, caps.MaxTextureWidth);

    d_supportNonSquareTex = !(caps.TextureCaps & D3DPTEXTURECAPS_SQUAREONLY);

    d_supportNPOTTex = !(caps.TextureCaps & D3DPTEXTURECAPS_POW2) ||
                       (caps.TextureCaps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL);

    d_defaultTarget = new ViewportTarget(*this);
    d_defaultRoot = new RenderingRoot(*d_defaultTarget);
}

//----------------------------------------------------------------------------//
Renderer::~Renderer()
{
    destroyAllGeometryBuffers();
    destroyAllTextureTargets();
    destroyAllTextures();

    delete d_defaultRoot;
    delete d_defaultTarget;
}

//----------------------------------------------------------------------------//
Size Renderer::getViewportSize()
{
    D3DVIEWPORT9 vp;

    if (FAILED(d_device->GetViewport(&vp)))
        throw RendererException("Renderer::getViewportSize - Unable "
                                "to access required view port information from "
                                "Direct3DDevice9.");
    else
	{
		Size size(vp.Width, vp.Height);
        return size;
	}
}

//----------------------------------------------------------------------------//
void Renderer::preD3DReset()
{
    // perform pre-reset on texture targets
    TextureTargetList::iterator target_iterator = d_textureTargets.begin();
    for (; target_iterator != d_textureTargets.end(); ++target_iterator)
        static_cast<TextureTarget*>(*target_iterator)->preD3DReset();

    // perform pre-reset on textures
    TextureList::iterator texture_iterator = d_textures.begin();
    for (; texture_iterator != d_textures.end(); ++texture_iterator)
        (*texture_iterator)->preD3DReset();
}

//----------------------------------------------------------------------------//
void Renderer::postD3DReset()
{
    // perform post-reset on textures
    TextureList::iterator texture_iterator = d_textures.begin();
    for (; texture_iterator != d_textures.end(); ++texture_iterator)
        (*texture_iterator)->postD3DReset();

    // perform post-reset on texture targets
    TextureTargetList::iterator target_iterator = d_textureTargets.begin();
    for (; target_iterator != d_textureTargets.end(); ++target_iterator)
       (*target_iterator)->postD3DReset();

    // notify system about the (possibly) new viewport size.
    System::getSingleton().notifyDisplaySizeChanged(getViewportSize());
}

//----------------------------------------------------------------------------//
LPDIRECT3DDEVICE9 Renderer::getDevice() const
{
    return d_device;
}

//----------------------------------------------------------------------------//
Texture& Renderer::createTexture(LPDIRECT3DTEXTURE9 texture)
{
    Texture* tex = new Texture(*this, texture);
    d_textures.push_back(tex);
    return *tex;
}

//----------------------------------------------------------------------------//
bool Renderer::supportsNonSquareTexture()
{
    return d_supportNonSquareTex;
}

//----------------------------------------------------------------------------//
bool Renderer::supportsNPOTTextures()
{
    return d_supportNPOTTex;
}

//----------------------------------------------------------------------------//
Size Renderer::getAdjustedSize(UINT width, UINT height)
{
    Size s;

    if (!d_supportNPOTTex)
    {
      /*  s.d_x  = getSizeNextPOT(sz.d_x);
        s.d_y = getSizeNextPOT(sz.d_y);*/
    }

    if (!d_supportNonSquareTex)
        s.d_x = s.d_y = max(width, height);

    return s;
}

//----------------------------------------------------------------------------//
float Renderer::getSizeNextPOT(float sz) const
{
    UINT size = static_cast<UINT>(sz);

    // if not power of 2
    if ((size & (size - 1)) || !size)
    {
        int log = 0;

        // get integer log of 'size' to base 2
        while (size >>= 1)
            ++log;

        // use log to calculate value to use as size.
        size = (2 << log);
    }

    return static_cast<float>(size);
}

